using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;

namespace FingerPoint.Android.UI
{
    [AddComponentMenu("Event/FingerPoint Input Module")]
    public class FPInputModule : BaseInputModule
    {
        private float m_PrevActionTime;
        private Vector2 m_LastMoveVector;
        private int m_ConsecutiveMoveCount = 0;

        private Vector2 m_LastMousePosition;
        private Vector2 m_MousePosition;

        private GameObject m_CurrentFocusedGameObject;

        private PointerEventData m_InputPointerEvent;

        private const float doubleClickTime = 0.3f;

        protected FPInputModule() { }

        [SerializeField]
        private string m_HorizontalAxis = "Horizontal";

        /// <summary>
        /// Name of the vertical axis for movement (if axis events are used).
        /// </summary>
        [SerializeField]
        private string m_VerticalAxis = "Vertical";

        /// <summary>
        /// Name of the submit button.
        /// </summary>
        [SerializeField]
        private string m_SubmitButton = "Submit";

        /// <summary>
        /// Name of the submit button.
        /// </summary>
        [SerializeField]
        private string m_CancelButton = "Cancel";

        [SerializeField]
        private float m_InputActionsPerSecond = 10;

        [SerializeField]
        private float m_RepeatDelay = 0.5f;

        [SerializeField]
        [FormerlySerializedAs("m_AllowActivationOnMobileDevice")]
        [HideInInspector]
        private bool m_ForceModuleActive;

        [Obsolete(
            "allowActivationOnMobileDevice has been deprecated. Use forceModuleActive instead (UnityUpgradable) -> forceModuleActive"
        )]
        public bool allowActivationOnMobileDevice
        {
            get { return m_ForceModuleActive; }
            set { m_ForceModuleActive = value; }
        }

        /// <summary>
        /// Force this module to be active.
        /// </summary>
        /// <remarks>
        /// If there is no module active with higher priority (ordered in the inspector) this module will be forced active even if valid enabling conditions are not met.
        /// </remarks>

        [Obsolete(
            "forceModuleActive has been deprecated. There is no need to force the module awake as StandaloneInputModule works for all platforms"
        )]
        public bool forceModuleActive
        {
            get { return m_ForceModuleActive; }
            set { m_ForceModuleActive = value; }
        }

        /// <summary>
        /// Number of keyboard / controller inputs allowed per second.
        /// </summary>
        public float inputActionsPerSecond
        {
            get { return m_InputActionsPerSecond; }
            set { m_InputActionsPerSecond = value; }
        }

        /// <summary>
        /// Delay in seconds before the input actions per second repeat rate takes effect.
        /// </summary>
        /// <remarks>
        /// If the same direction is sustained, the inputActionsPerSecond property can be used to control the rate at which events are fired. However, it can be desirable that the first repetition is delayed, so the user doesn't get repeated actions by accident.
        /// </remarks>
        public float repeatDelay
        {
            get { return m_RepeatDelay; }
            set { m_RepeatDelay = value; }
        }

        /// <summary>
        /// Name of the horizontal axis for movement (if axis events are used).
        /// </summary>
        public string horizontalAxis
        {
            get { return m_HorizontalAxis; }
            set { m_HorizontalAxis = value; }
        }

        /// <summary>
        /// Name of the vertical axis for movement (if axis events are used).
        /// </summary>
        public string verticalAxis
        {
            get { return m_VerticalAxis; }
            set { m_VerticalAxis = value; }
        }

        /// <summary>
        /// Maximum number of input events handled per second.
        /// </summary>
        public string submitButton
        {
            get { return m_SubmitButton; }
            set { m_SubmitButton = value; }
        }

        /// <summary>
        /// Input manager name for the 'cancel' button.
        /// </summary>
        public string cancelButton
        {
            get { return m_CancelButton; }
            set { m_CancelButton = value; }
        }

        protected override void Awake()
        {
            base.Awake();
            inputOverride = GetComponent<FPInput>();
            if (inputOverride == null)
            {
                inputOverride = gameObject.AddComponent<FPInput>();
            }
        }

        private bool ShouldIgnoreEventsOnNoFocus()
        {
#if UNITY_EDITOR
            return !UnityEditor.EditorApplication.isRemoteConnected;
#else
            return true;
#endif
        }

        public override void UpdateModule()
        {
            if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
            {
                if (
                    m_InputPointerEvent != null
                    && m_InputPointerEvent.pointerDrag != null
                    && m_InputPointerEvent.dragging
                )
                {
                    ReleaseMouse(
                        m_InputPointerEvent,
                        m_InputPointerEvent.pointerCurrentRaycast.gameObject
                    );
                }

                m_InputPointerEvent = null;

                return;
            }

            m_LastMousePosition = m_MousePosition;
            m_MousePosition = input.mousePosition;
        }

        private void ReleaseMouse(PointerEventData pointerEvent, GameObject currentOverGo)
        {
            ExecuteEvents.Execute(
                pointerEvent.pointerPress,
                pointerEvent,
                ExecuteEvents.pointerUpHandler
            );

            var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(
                currentOverGo
            );

            // PointerClick and Drop events
            if (pointerEvent.pointerClick == pointerClickHandler && pointerEvent.eligibleForClick)
            {
                ExecuteEvents.Execute(
                    pointerEvent.pointerClick,
                    pointerEvent,
                    ExecuteEvents.pointerClickHandler
                );
            }
            if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
            {
                ExecuteEvents.ExecuteHierarchy(
                    currentOverGo,
                    pointerEvent,
                    ExecuteEvents.dropHandler
                );
            }

            pointerEvent.eligibleForClick = false;
            pointerEvent.pointerPress = null;
            pointerEvent.rawPointerPress = null;
            pointerEvent.pointerClick = null;

            if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
                ExecuteEvents.Execute(
                    pointerEvent.pointerDrag,
                    pointerEvent,
                    ExecuteEvents.endDragHandler
                );

            pointerEvent.dragging = false;
            pointerEvent.pointerDrag = null;

            // redo pointer enter / exit to refresh state
            // so that if we moused over something that ignored it before
            // due to having pressed on something else
            // it now gets it.
            if (currentOverGo != pointerEvent.pointerEnter)
            {
                HandlePointerExitAndEnter(pointerEvent, null);
                HandlePointerExitAndEnter(pointerEvent, currentOverGo);
            }

            m_InputPointerEvent = pointerEvent;
        }

        public override bool ShouldActivateModule()
        {
            if (!base.ShouldActivateModule())
                return false;

            var shouldActivate = m_ForceModuleActive;
            shouldActivate |= input.GetButtonDown(m_SubmitButton);
            shouldActivate |= input.GetButtonDown(m_CancelButton);
            shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_HorizontalAxis), 0.0f);
            shouldActivate |= !Mathf.Approximately(input.GetAxisRaw(m_VerticalAxis), 0.0f);
            shouldActivate |= (m_MousePosition - m_LastMousePosition).sqrMagnitude > 0.0f;
            shouldActivate |= input.GetMouseButtonDown(0);

            if (input.touchCount > 0)
                shouldActivate = true;

            return shouldActivate;
        }

        /// <summary>
        /// See BaseInputModule.
        /// </summary>
        public override void ActivateModule()
        {
            if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
                return;

            base.ActivateModule();
            m_MousePosition = input.mousePosition;
            m_LastMousePosition = input.mousePosition;

            var toSelect = eventSystem.currentSelectedGameObject;
            if (toSelect == null)
                toSelect = eventSystem.firstSelectedGameObject;

            eventSystem.SetSelectedGameObject(toSelect, GetBaseEventData());
        }

        /// <summary>
        /// See BaseInputModule.
        /// </summary>
        public override void DeactivateModule()
        {
            base.DeactivateModule();
            ClearSelection();
        }

        protected void ClearSelection()
        {
            BaseEventData baseEventData = GetBaseEventData();
            foreach (PointerEventData value in m_PointerData.Values)
            {
                HandlePointerExitAndEnter(value, null);
            }

            m_PointerData.Clear();
            base.eventSystem.SetSelectedGameObject(null, baseEventData);
        }

        public override void Process()
        {
            if (!eventSystem.isFocused && ShouldIgnoreEventsOnNoFocus())
                return;

            bool usedEvent = SendUpdateEventToSelectedObject();

            if (input.mousePresent)
            {
                ProcessMouseEvent();
            }
            else
            {
                PointerEventData data = GetFingPointPointerEventData();
                ProcessTouchPress(data, input.GetMouseButtonDown(0), input.GetMouseButtonUp(0));
            }

            if (eventSystem.sendNavigationEvents)
            {
                if (!usedEvent)
                    usedEvent |= SendMoveEventToSelectedObject();

                if (!usedEvent)
                    SendSubmitEventToSelectedObject();
            }
        }

        protected PointerEventData GetFingPointPointerEventData()
        {
            PointerEventData data;
            bool pointerData = GetPointerData(-1, out data, create: true);
            data.Reset();
            if (pointerData)
            {
                data.position = base.input.mousePosition;
            }
            Vector2 mousePosition = base.input.mousePosition;
            data.delta = mousePosition - data.position;
            data.position = mousePosition;
            data.scrollDelta = base.input.mouseScrollDelta;
            data.button = PointerEventData.InputButton.Left;
            base.eventSystem.RaycastAll(data, m_RaycastResultCache);
            RaycastResult pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
            data.pointerCurrentRaycast = pointerCurrentRaycast;
            m_RaycastResultCache.Clear();
            return data;
        }

        /// <summary>
        /// This method is called by Unity whenever a touch event is processed. Override this method with a custom implementation to process touch events yourself.
        /// </summary>
        /// <param name="pointerEvent">Event data relating to the touch event, such as position and ID to be passed to the touch event destination object.</param>
        /// <param name="pressed">This is true for the first frame of a touch event, and false thereafter. This can therefore be used to determine the instant a touch event occurred.</param>
        /// <param name="released">This is true only for the last frame of a touch event.</param>
        /// <remarks>
        /// This method can be overridden in derived classes to change how touch press events are handled.
        /// </remarks>
        protected void ProcessTouchPress(PointerEventData pointerEvent, bool pressed, bool released)
        {
            var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;

            // PointerDown notification
            if (pressed)
            {
                pointerEvent.eligibleForClick = true;
                pointerEvent.delta = Vector2.zero;
                pointerEvent.dragging = false;
                pointerEvent.useDragThreshold = true;
                pointerEvent.pressPosition = pointerEvent.position;
                pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;

                DeselectIfSelectionChanged(currentOverGo, pointerEvent);

                if (pointerEvent.pointerEnter != currentOverGo)
                {
                    // send a pointer enter to the touched element if it isn't the one to select...
                    HandlePointerExitAndEnter(pointerEvent, currentOverGo);
                    pointerEvent.pointerEnter = currentOverGo;
                }

                var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
                if (resetDiffTime >= doubleClickTime)
                {
                    pointerEvent.clickCount = 0;
                }

                // search for the control that will receive the press
                // if we can't find a press handler set the press
                // handler to be what would receive a click.
                var newPressed = ExecuteEvents.ExecuteHierarchy(
                    currentOverGo,
                    pointerEvent,
                    ExecuteEvents.pointerDownHandler
                );

                var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);

                // didnt find a press handler... search for a click handler
                if (newPressed == null)
                    newPressed = newClick;

                // Debug.Log("Pressed: " + newPressed);

                float time = Time.unscaledTime;

                if (newPressed == pointerEvent.lastPress)
                {
                    var diffTime = time - pointerEvent.clickTime;
                    if (diffTime < doubleClickTime)
                        ++pointerEvent.clickCount;
                    else
                        pointerEvent.clickCount = 1;

                    pointerEvent.clickTime = time;
                }
                else
                {
                    pointerEvent.clickCount = 1;
                }

                pointerEvent.pointerPress = newPressed;
                pointerEvent.rawPointerPress = currentOverGo;
                pointerEvent.pointerClick = newClick;

                pointerEvent.clickTime = time;

                // Save the drag handler as well
                pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(
                    currentOverGo
                );

                // Debug.Log(pointerEvent.pointerDrag);

                if (pointerEvent.pointerDrag != null)
                    ExecuteEvents.Execute(
                        pointerEvent.pointerDrag,
                        pointerEvent,
                        ExecuteEvents.initializePotentialDrag
                    );
            }

            // PointerUp notification
            if (released)
            {
                // Debug.Log("Executing pressup on: " + pointer.pointerPress);
                ExecuteEvents.Execute(
                    pointerEvent.pointerPress,
                    pointerEvent,
                    ExecuteEvents.pointerUpHandler
                );

                // Debug.Log("KeyCode: " + pointer.eventData.keyCode);

                // see if we mouse up on the same element that we clicked on...
                var pointerClickHandler = ExecuteEvents.GetEventHandler<IPointerClickHandler>(
                    currentOverGo
                );

                // PointerClick and Drop events
                if (
                    pointerEvent.pointerClick == pointerClickHandler
                    && pointerEvent.eligibleForClick
                )
                {
                    ExecuteEvents.Execute(
                        pointerEvent.pointerClick,
                        pointerEvent,
                        ExecuteEvents.pointerClickHandler
                    );
                }

                if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
                {
                    ExecuteEvents.ExecuteHierarchy(
                        currentOverGo,
                        pointerEvent,
                        ExecuteEvents.dropHandler
                    );
                }

                pointerEvent.eligibleForClick = false;
                pointerEvent.pointerPress = null;
                pointerEvent.rawPointerPress = null;
                pointerEvent.pointerClick = null;

                if (pointerEvent.pointerDrag != null && pointerEvent.dragging)
                    ExecuteEvents.Execute(
                        pointerEvent.pointerDrag,
                        pointerEvent,
                        ExecuteEvents.endDragHandler
                    );

                pointerEvent.dragging = false;
                pointerEvent.pointerDrag = null;

                // send exit events as we need to simulate this on touch up on touch device
                ExecuteEvents.ExecuteHierarchy(
                    pointerEvent.pointerEnter,
                    pointerEvent,
                    ExecuteEvents.pointerExitHandler
                );
                pointerEvent.pointerEnter = null;
            }

            m_InputPointerEvent = pointerEvent;
        }

        /// <summary>
        /// Calculate and send a submit event to the current selected object.
        /// </summary>
        /// <returns>If the submit event was used by the selected object.</returns>
        protected bool SendSubmitEventToSelectedObject()
        {
            if (eventSystem.currentSelectedGameObject == null)
                return false;

            var data = GetBaseEventData();
            if (input.GetButtonDown(m_SubmitButton))
                ExecuteEvents.Execute(
                    eventSystem.currentSelectedGameObject,
                    data,
                    ExecuteEvents.submitHandler
                );

            if (input.GetButtonDown(m_CancelButton))
                ExecuteEvents.Execute(
                    eventSystem.currentSelectedGameObject,
                    data,
                    ExecuteEvents.cancelHandler
                );
            return data.used;
        }

        private Vector2 GetRawMoveVector()
        {
            Vector2 move = Vector2.zero;
            move.x = input.GetAxisRaw(m_HorizontalAxis);
            move.y = input.GetAxisRaw(m_VerticalAxis);

            if (input.GetButtonDown(m_HorizontalAxis))
            {
                if (move.x < 0)
                    move.x = -1f;
                if (move.x > 0)
                    move.x = 1f;
            }
            if (input.GetButtonDown(m_VerticalAxis))
            {
                if (move.y < 0)
                    move.y = -1f;
                if (move.y > 0)
                    move.y = 1f;
            }
            return move;
        }

        /// <summary>
        /// Calculate and send a move event to the current selected object.
        /// </summary>
        /// <returns>If the move event was used by the selected object.</returns>
        protected bool SendMoveEventToSelectedObject()
        {
            float time = Time.unscaledTime;

            Vector2 movement = GetRawMoveVector();
            if (Mathf.Approximately(movement.x, 0f) && Mathf.Approximately(movement.y, 0f))
            {
                m_ConsecutiveMoveCount = 0;
                return false;
            }

            bool similarDir = (Vector2.Dot(movement, m_LastMoveVector) > 0);

            // If direction didn't change at least 90 degrees, wait for delay before allowing consequtive event.
            if (similarDir && m_ConsecutiveMoveCount == 1)
            {
                if (time <= m_PrevActionTime + m_RepeatDelay)
                    return false;
            }
            // If direction changed at least 90 degree, or we already had the delay, repeat at repeat rate.
            else
            {
                if (time <= m_PrevActionTime + 1f / m_InputActionsPerSecond)
                    return false;
            }

            var axisEventData = GetAxisEventData(movement.x, movement.y, 0.6f);

            if (axisEventData.moveDir != MoveDirection.None)
            {
                ExecuteEvents.Execute(
                    eventSystem.currentSelectedGameObject,
                    axisEventData,
                    ExecuteEvents.moveHandler
                );
                if (!similarDir)
                    m_ConsecutiveMoveCount = 0;
                m_ConsecutiveMoveCount++;
                m_PrevActionTime = time;
                m_LastMoveVector = movement;
            }
            else
            {
                m_ConsecutiveMoveCount = 0;
            }

            return axisEventData.used;
        }

        [Obsolete("This method is no longer checked, overriding it with return true does nothing!")]
        protected virtual bool ForceAutoSelect()
        {
            return false;
        }

        /// <summary>
        /// Process all mouse events.
        /// </summary>
        protected void ProcessMouseEvent()
        {
            var mouseData = GetMousePointerEventData();
            var leftButtonData = mouseData
                .GetButtonState(PointerEventData.InputButton.Left)
                .eventData;

            m_CurrentFocusedGameObject = leftButtonData.buttonData.pointerCurrentRaycast.gameObject;
            // Debug.Log(m_CurrentFocusedGameObject);
            // Process the first mouse button fully
            ProcessMousePress(leftButtonData);
            ProcessMove(leftButtonData.buttonData);
            ProcessDrag(leftButtonData.buttonData);

            // Now process right / middle clicks
            ProcessMousePress(
                mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData
            );
            ProcessDrag(
                mouseData.GetButtonState(PointerEventData.InputButton.Right).eventData.buttonData
            );
            ProcessMousePress(
                mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData
            );
            ProcessDrag(
                mouseData.GetButtonState(PointerEventData.InputButton.Middle).eventData.buttonData
            );

            if (!Mathf.Approximately(leftButtonData.buttonData.scrollDelta.sqrMagnitude, 0.0f))
            {
                var scrollHandler = ExecuteEvents.GetEventHandler<IScrollHandler>(
                    leftButtonData.buttonData.pointerCurrentRaycast.gameObject
                );
                ExecuteEvents.ExecuteHierarchy(
                    scrollHandler,
                    leftButtonData.buttonData,
                    ExecuteEvents.scrollHandler
                );
            }
        }

        private readonly MouseState m_MouseState = new MouseState();

        protected MouseState GetMousePointerEventData()
        {
            PointerEventData data;
            bool pointerData = GetPointerData(-1, out data, create: true);
            data.Reset();
            if (pointerData)
            {
                data.position = base.input.mousePosition;
            }

            Vector2 mousePosition = base.input.mousePosition;
            if (Cursor.lockState == CursorLockMode.Locked)
            {
                data.position = new Vector2(-1f, -1f);
                data.delta = Vector2.zero;
            }
            else
            {
                data.delta = mousePosition - data.position;
                data.position = mousePosition;
            }

            data.scrollDelta = base.input.mouseScrollDelta;
            data.button = PointerEventData.InputButton.Left;
            base.eventSystem.RaycastAll(data, m_RaycastResultCache);
            RaycastResult pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
            data.pointerCurrentRaycast = pointerCurrentRaycast;
            m_RaycastResultCache.Clear();
            GetPointerData(-2, out var data2, create: true);
            data2.Reset();
            CopyFromTo(data, data2);
            data2.button = PointerEventData.InputButton.Right;
            // data2.
            GetPointerData(-3, out var data3, create: true);
            data3.Reset();
            CopyFromTo(data, data3);
            data3.button = PointerEventData.InputButton.Middle;
            m_MouseState.SetButtonState(
                PointerEventData.InputButton.Left,
                StateForMouseButton(0),
                data
            );
            m_MouseState.SetButtonState(
                PointerEventData.InputButton.Right,
                StateForMouseButton(1),
                data2
            );
            m_MouseState.SetButtonState(
                PointerEventData.InputButton.Middle,
                StateForMouseButton(2),
                data3
            );
            return m_MouseState;
        }

        protected PointerEventData.FramePressState StateForMouseButton(int buttonId)
        {
            bool mouseButtonDown = base.input.GetMouseButtonDown(buttonId);
            bool mouseButtonUp = base.input.GetMouseButtonUp(buttonId);
            if (mouseButtonDown && mouseButtonUp)
            {
                return PointerEventData.FramePressState.PressedAndReleased;
            }

            if (mouseButtonDown)
            {
                // Debug.Log("mouseButtonDown");
                return PointerEventData.FramePressState.Pressed;
            }

            if (mouseButtonUp)
            {
                // Debug.Log("mouseButtonUp");
                return PointerEventData.FramePressState.Released;
            }

            return PointerEventData.FramePressState.NotChanged;
        }

        protected void CopyFromTo(PointerEventData from, PointerEventData to)
        {
            to.position = from.position;
            to.delta = from.delta;
            to.scrollDelta = from.scrollDelta;
            to.pointerCurrentRaycast = from.pointerCurrentRaycast;
            to.pointerEnter = from.pointerEnter;
            to.pressure = from.pressure;
            to.tangentialPressure = from.tangentialPressure;
            to.altitudeAngle = from.altitudeAngle;
            to.azimuthAngle = from.azimuthAngle;
            to.twist = from.twist;
            to.radius = from.radius;
            to.radiusVariance = from.radiusVariance;
        }

        protected Dictionary<int, PointerEventData> m_PointerData =
            new Dictionary<int, PointerEventData>();

        protected bool GetPointerData(int id, out PointerEventData data, bool create)
        {
            if (!m_PointerData.TryGetValue(id, out data) && create)
            {
                data = new PointerEventData(base.eventSystem) { pointerId = id };
                m_PointerData.Add(id, data);
                return true;
            }

            return false;
        }

        protected virtual void ProcessMove(PointerEventData pointerEvent)
        {
            GameObject newEnterTarget = (
                (Cursor.lockState == CursorLockMode.Locked)
                    ? null
                    : pointerEvent.pointerCurrentRaycast.gameObject
            );
            HandlePointerExitAndEnter(pointerEvent, newEnterTarget);
        }

        private static bool ShouldStartDrag(
            Vector2 pressPos,
            Vector2 currentPos,
            float threshold,
            bool useDragThreshold
        )
        {
            if (!useDragThreshold)
            {
                return true;
            }

            return (pressPos - currentPos).sqrMagnitude >= threshold * threshold;
        }

        protected virtual void ProcessDrag(PointerEventData pointerEvent)
        {
            if (
                !pointerEvent.IsPointerMoving()
                || Cursor.lockState == CursorLockMode.Locked
                || pointerEvent.pointerDrag == null
            )
            {
                return;
            }

            if (
                !pointerEvent.dragging
                && ShouldStartDrag(
                    pointerEvent.pressPosition,
                    pointerEvent.position,
                    base.eventSystem.pixelDragThreshold,
                    pointerEvent.useDragThreshold
                )
            )
            {
                ExecuteEvents.Execute(
                    pointerEvent.pointerDrag,
                    pointerEvent,
                    ExecuteEvents.beginDragHandler
                );
                pointerEvent.dragging = true;
            }

            if (pointerEvent.dragging)
            {
                if (pointerEvent.pointerPress != pointerEvent.pointerDrag)
                {
                    ExecuteEvents.Execute(
                        pointerEvent.pointerPress,
                        pointerEvent,
                        ExecuteEvents.pointerUpHandler
                    );
                    pointerEvent.eligibleForClick = false;
                    pointerEvent.pointerPress = null;
                    pointerEvent.rawPointerPress = null;
                }

                ExecuteEvents.Execute(
                    pointerEvent.pointerDrag,
                    pointerEvent,
                    ExecuteEvents.dragHandler
                );
            }
        }

        protected bool SendUpdateEventToSelectedObject()
        {
            if (eventSystem.currentSelectedGameObject == null)
                return false;
            // Debug.Log(eventSystem.currentSelectedGameObject);
            var data = GetBaseEventData();
            ExecuteEvents.Execute(
                eventSystem.currentSelectedGameObject,
                data,
                ExecuteEvents.updateSelectedHandler
            );
            return data.used;
        }

        /// <summary>
        /// Calculate and process any mouse button state changes.
        /// </summary>
        protected void ProcessMousePress(MouseButtonEventData data)
        {
            var pointerEvent = data.buttonData;
            var currentOverGo = pointerEvent.pointerCurrentRaycast.gameObject;

            // PointerDown notification
            if (data.PressedThisFrame())
            {
                pointerEvent.eligibleForClick = true;
                pointerEvent.delta = Vector2.zero;
                pointerEvent.dragging = false;
                pointerEvent.useDragThreshold = true;
                pointerEvent.pressPosition = pointerEvent.position;
                pointerEvent.pointerPressRaycast = pointerEvent.pointerCurrentRaycast;

                DeselectIfSelectionChanged(currentOverGo, pointerEvent);

                var resetDiffTime = Time.unscaledTime - pointerEvent.clickTime;
                if (resetDiffTime >= doubleClickTime)
                {
                    pointerEvent.clickCount = 0;
                }

                // search for the control that will receive the press
                // if we can't find a press handler set the press
                // handler to be what would receive a click.
                var newPressed = ExecuteEvents.ExecuteHierarchy(
                    currentOverGo,
                    pointerEvent,
                    ExecuteEvents.pointerDownHandler
                );
                var newClick = ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentOverGo);

                // didnt find a press handler... search for a click handler
                if (newPressed == null)
                    newPressed = newClick;

                // Debug.Log("Pressed: " + newPressed);

                float time = Time.unscaledTime;

                if (newPressed == pointerEvent.lastPress)
                {
                    var diffTime = time - pointerEvent.clickTime;
                    if (diffTime < doubleClickTime)
                        ++pointerEvent.clickCount;
                    else
                        pointerEvent.clickCount = 1;

                    pointerEvent.clickTime = time;
                }
                else
                {
                    pointerEvent.clickCount = 1;
                }

                pointerEvent.pointerPress = newPressed;
                pointerEvent.rawPointerPress = currentOverGo;
                pointerEvent.pointerClick = newClick;

                pointerEvent.clickTime = time;

                // Save the drag handler as well
                pointerEvent.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(
                    currentOverGo
                );

                if (pointerEvent.pointerDrag != null)
                    ExecuteEvents.Execute(
                        pointerEvent.pointerDrag,
                        pointerEvent,
                        ExecuteEvents.initializePotentialDrag
                    );

                m_InputPointerEvent = pointerEvent;
            }

            // PointerUp notification
            if (data.ReleasedThisFrame())
            {
                ReleaseMouse(pointerEvent, currentOverGo);
            }
        }

        protected GameObject GetCurrentFocusedGameObject()
        {
            return m_CurrentFocusedGameObject;
        }

        protected void DeselectIfSelectionChanged(
            GameObject currentOverGo,
            BaseEventData pointerEvent
        )
        {
            if (
                ExecuteEvents.GetEventHandler<ISelectHandler>(currentOverGo)
                != base.eventSystem.currentSelectedGameObject
            )
            {
                base.eventSystem.SetSelectedGameObject(null, pointerEvent);
            }
        }

        protected class ButtonState
        {
            private PointerEventData.InputButton m_Button;

            private MouseButtonEventData m_EventData;

            public MouseButtonEventData eventData
            {
                get { return m_EventData; }
                set { m_EventData = value; }
            }

            public PointerEventData.InputButton button
            {
                get { return m_Button; }
                set { m_Button = value; }
            }
        }

        protected class MouseState
        {
            private List<ButtonState> m_TrackedButtons = new List<ButtonState>();

            public bool AnyPressesThisFrame()
            {
                int count = m_TrackedButtons.Count;
                for (int i = 0; i < count; i++)
                {
                    if (m_TrackedButtons[i].eventData.PressedThisFrame())
                    {
                        return true;
                    }
                }

                return false;
            }

            public bool AnyReleasesThisFrame()
            {
                int count = m_TrackedButtons.Count;
                for (int i = 0; i < count; i++)
                {
                    if (m_TrackedButtons[i].eventData.ReleasedThisFrame())
                    {
                        return true;
                    }
                }

                return false;
            }

            public ButtonState GetButtonState(PointerEventData.InputButton button)
            {
                ButtonState buttonState = null;
                int count = m_TrackedButtons.Count;
                for (int i = 0; i < count; i++)
                {
                    if (m_TrackedButtons[i].button == button)
                    {
                        buttonState = m_TrackedButtons[i];
                        break;
                    }
                }

                if (buttonState == null)
                {
                    buttonState = new ButtonState
                    {
                        button = button,
                        eventData = new MouseButtonEventData()
                    };
                    m_TrackedButtons.Add(buttonState);
                }

                return buttonState;
            }

            public void SetButtonState(
                PointerEventData.InputButton button,
                PointerEventData.FramePressState stateForMouseButton,
                PointerEventData data
            )
            {
                ButtonState buttonState = GetButtonState(button);
                buttonState.eventData.buttonState = stateForMouseButton;
                buttonState.eventData.buttonData = data;
            }
        }

        public class MouseButtonEventData
        {
            public PointerEventData.FramePressState buttonState;

            public PointerEventData buttonData;

            public bool PressedThisFrame()
            {
                // if (buttonState != PointerEventData.FramePressState.NotChanged)
                // {
                //     Debug.Log("PressedThisFrame  " + buttonState);
                // }
                if (buttonState != PointerEventData.FramePressState.Pressed)
                {
                    return buttonState == PointerEventData.FramePressState.PressedAndReleased;
                }
                // Debug.Log("PressedThisFrame  " + buttonState);
                return true;
            }

            public bool ReleasedThisFrame()
            {
                // if (buttonState != PointerEventData.FramePressState.NotChanged)
                // {
                //     Debug.Log("ReleasedThisFrame  " + buttonState);
                // }

                if (buttonState != PointerEventData.FramePressState.Released)
                {
                    return buttonState == PointerEventData.FramePressState.PressedAndReleased;
                }

                return true;
            }
        }
    }
}
